#region License

// Dimebrain TweetSharp
// (www.dimebrain.com)
// 
// The MIT License
// 
// Copyright (c) 2009 Dimebrain
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 

#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using Dimebrain.TweetSharp.Core.Caching;

namespace Dimebrain.TweetSharp.Core.Web.Query
{
    partial class WebQueryBase
    {
        protected virtual void PostAsyncRequestCallback(IAsyncResult asyncResult)
        {
            WebRequest request;
            byte[] content;
            Triplet<IClientCache, object, string> store;

            var state = asyncResult.AsyncState as Pair<WebRequest, byte[]>;
            if (state == null)
            {
                // no expiration specified
                if (asyncResult is Pair<WebRequest, Triplet<byte[], IClientCache, string>>)
                {
                    var cacheScheme = (Pair<WebRequest, Triplet<byte[], IClientCache, string>>)asyncResult;
                    var cache = cacheScheme.Second.Second;

                    var url = cacheScheme.First.RequestUri.ToString();
                    var prefix = cacheScheme.Second.Third;
                    var key = CreateCacheKey(prefix, url);

                    var fetch = cache.Get<string>(key);
                    if (fetch != null)
                    {
                        var args = new WebQueryResponseEventArgs(fetch);
                        OnQueryResponse(args);
                        return;
                    }

                    request = cacheScheme.First;
                    content = cacheScheme.Second.First;
                    store = new Triplet<IClientCache, object, string> { First = cache, Second = null, Third = prefix };
                }
                else
                    // absolute expiration specified
                    if (asyncResult is Pair<WebRequest, Pair<byte[], Triplet<IClientCache, DateTime, string>>>)
                    {
                        var cacheScheme =
                            (Pair<WebRequest, Pair<byte[], Triplet<IClientCache, DateTime, string>>>)asyncResult;
                        var url = cacheScheme.First.RequestUri.ToString();
                        var cache = cacheScheme.Second.Second.First;
                        var expiry = cacheScheme.Second.Second.Second;

                        var prefix = cacheScheme.Second.Second.Third;
                        var key = CreateCacheKey(prefix, url);

                        var fetch = cache.Get<string>(key);
                        if (fetch != null)
                        {
                            var args = new WebQueryResponseEventArgs(fetch);
                            OnQueryResponse(args);
                            return;
                        }

                        request = cacheScheme.First;
                        content = cacheScheme.Second.First;
                        store = new Triplet<IClientCache, object, string> { First = cache, Second = expiry, Third = prefix };
                    }
                    else
                        // sliding expiration specified
                        if (asyncResult is Pair<WebRequest, Pair<byte[], Triplet<IClientCache, TimeSpan, string>>>)
                        {
                            var cacheScheme =
                                (Pair<WebRequest, Pair<byte[], Triplet<IClientCache, TimeSpan, string>>>)asyncResult;
                            var url = cacheScheme.First.RequestUri.ToString();
                            var cache = cacheScheme.Second.Second.First;
                            var expiry = cacheScheme.Second.Second.Second;

                            var prefix = cacheScheme.Second.Second.Third;
                            var key = CreateCacheKey(prefix, url);

                            var fetch = cache.Get<string>(key);
                            if (fetch != null)
                            {
                                var args = new WebQueryResponseEventArgs(fetch);
                                OnQueryResponse(args);
                                return;
                            }

                            request = cacheScheme.First;
                            content = cacheScheme.Second.First;
                            store = new Triplet<IClientCache, object, string>
                                    {First = cache, Second = expiry, Third = prefix};
                        }
                        else
                        {
                            // unrecognized state signature
                            throw new ArgumentNullException("asyncResult",
                                                            "The asynchronous post failed to return its state");
                        }
            }
            else
            {
                request = state.First;
                content = state.Second;
                store = null;
            }

            // no cached response
            using (var stream = request.EndGetRequestStream(asyncResult))
            {
                stream.Write(content, 0, content.Length);
                stream.Close();

                request.BeginGetResponse(PostAsyncResponseCallback,
                                         new Pair<WebRequest, Triplet<IClientCache, object, string>>
                                         {First = request, Second = store});
            }
        }

        protected virtual void PostAsyncResponseCallback(IAsyncResult asyncResult)
        {
            var state = asyncResult.AsyncState as Pair<WebRequest, Triplet<IClientCache, object, string>>;
            if (state == null)
            {
                throw new ArgumentNullException("asyncResult", "The asynchronous post failed to return its state");
            }

            var request = state.First;
            if (request == null)
            {
                throw new ArgumentNullException("asyncResult", "The asynchronous post failed to return a request");
            }

            try
            {
                using (var response = request.EndGetResponse(asyncResult))
                {
                    using (var reader = new StreamReader(response.GetResponseStream()))
                    {
                        var result = reader.ReadToEnd();
                        Response = response;

                        if (state.Second != null)
                        {
                            var cache = state.Second.First;
                            var expiry = state.Second.Second;
                            var url = request.RequestUri.ToString();

                            var prefix = state.Second.Third;
                            var key = CreateCacheKey(prefix, url);

                            if (expiry is DateTime)
                            {
                                // absolute
                                cache.Insert(key, result, (DateTime) expiry);
                            }

                            if (expiry is TimeSpan)
                            {
                                // sliding
                                cache.Insert(key, result, (TimeSpan) expiry);
                            }
                        }

                        var args = new WebQueryResponseEventArgs(result);
                        OnQueryResponse(args);
                    }
                }
            }
            catch (WebException ex)
            {
                HandleWebException(ex);
            }
        }

        protected virtual IAsyncResult ExecutePostAsync(string url)
        {
            Response = null;

            byte[] content;
            var request = BuildPostWebRequest(url, out content);

            var state = new Pair<WebRequest, byte[]> {First = request, Second = content};
            var args = new WebQueryRequestEventArgs(url);
            OnQueryRequest(args);

            return request.BeginGetRequestStream(PostAsyncRequestCallback, state);
        }

        protected virtual void ExecutePostAsync(string url, IEnumerable<HttpPostParameter> parameters)
        {
            Response = null;

            // Credit to Sean Erickson for providing a clean 
            // implementation of multi-part data posting
            byte[] content;
            var request = BuildMultiPartFormRequest(url, parameters, out content);

            var state = new Pair<WebRequest, byte[]> {First = request, Second = content};
            var args = new WebQueryRequestEventArgs(url);
            OnQueryRequest(args);

            request.BeginGetRequestStream(PostAsyncRequestCallback, state);
        }
    }
}